#include "../../h/apue.h"
#include "../utils.h"
#include <fcntl.h>
#include <dirent.h>
#include <limits.h>
#include <time.h>

#define FTW_F 1     /* file other than dir */
#define FTW_D 2     /* is dir*/
#define FTW_DNR 3   /* dir that can't be read */
#define FTW_NS 4    /* file that can't stat */

static char * fullpath;
static size_t pathlen;

typedef int (* Myfunc)(const char *, const struct stat *, int);

static int myfunc(const char *, const struct stat *, int);

static int myftw(char *, Myfunc);

static int dopath(char *, Myfunc);

static long nreg, ndir, nblk, nchr, nfifo, nsock, nslink, ntot;

int main(int argc, char ** argv)
{
    int ret;
    struct stat stbuf;
    clock_t s1, s2;

    s1 = clock();

    if (argc != 2)
        err_quit("Usage: %s <start-pathname>", argv[0]);

    if (stat(argv[1], &stbuf) < 0)
        err_sys("stat %s failed", argv[1]);

    if (S_ISDIR(stbuf.st_mode) == 0)
        err_quit("%s should be dir.", argv[1]);

    ret = myftw(argv[1], myfunc);
    ntot = nreg + ndir + nblk + nchr + nfifo + nsock + nslink;
    if (ntot == 0)
        ntot = 1;

    printf("Regular files = %7ld, %5.2f %%\n", nreg, nreg*100.0/ntot);
    printf("Dir files     = %7ld, %5.2f %%\n", ndir, ndir*100.0/ntot);
    printf("Block special = %7ld, %5.2f %%\n", nblk, nblk*100.0/ntot);
    printf("Char special  = %7ld, %5.2f %%\n", nchr, nchr*100.0/ntot);
    printf("FIFOs         = %7ld, %5.2f %%\n", nfifo, nfifo*100.0/ntot);
    printf("symbolic links= %7ld, %5.2f %%\n", nslink, nslink*100.0/ntot);
    printf("sockets       = %7ld, %5.2f %%\n", nsock, nsock*100.0/ntot);

    s2 = clock();
    printf("total use: %lfs", ((1.0 * s2 -s1)/CLOCKS_PER_SEC));

    return 0;
}

int myftw(char *pathname, Myfunc myfunc)
{
    fullpath = my_path_alloc(&pathlen);
    if (strlen(pathname) >= pathlen)
    {
        pathlen *= 2;
        if ((fullpath = realloc(fullpath, pathlen)) == NULL)
            err_sys("realloc failed");
    }

    strcpy(fullpath, pathname);
    chdir(pathname);


    return dopath("./", myfunc);
}

int dopath(char * path, Myfunc func)
{
    struct stat statbuf;
    struct dirent * dirp;
    DIR *dp;
    int ret, n;

    if (lstat(path, &statbuf) < 0)
        return func(fullpath, NULL, FTW_NS);

    if (S_ISDIR(statbuf.st_mode) == 0)
        return func(fullpath, &statbuf, FTW_F);

    if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
        return ret;

    n = strlen(fullpath);
    if (n + NAME_MAX + 2 > pathlen)
    {
        pathlen *= 2;
        if ((fullpath = realloc(fullpath, pathlen)) == NULL)
            err_sys("realloc failed");
    }

    if ( chdir(path) < 0)
    {
        err_ret("chdir %s failed", fullpath);
        return 0;
    }

    //if ((dp = opendir(path)) == NULL)
    if ((dp = opendir("./")) == NULL)
        return func(fullpath, &statbuf, FTW_DNR);

    fullpath[n++] = '/';
    fullpath[n] = 0;

    while ((dirp = readdir(dp)) != NULL)
    {
        if (strcmp(dirp->d_name, ".") == 0 ||
            strcmp(dirp->d_name, "..") == 0)
            continue;

        strcpy(fullpath + n, dirp->d_name);
        if ((ret = dopath(dirp->d_name, func)) != 0)
            break;
    }
    chdir("../");

    fullpath[n-1] = 0;
    if (closedir(dp) < 0)
        err_ret("can't close dir %s.", fullpath);

    return ret;
}

int myfunc(const char * path, const struct stat * statp, int type)
{
    switch (type)
    {
        case FTW_F:
            switch (statp->st_mode & S_IFMT)
            {
                case S_IFREG:   nreg++;     break;
                case S_IFBLK:   nblk++;     break;
                case S_IFCHR:   nchr++;     break;
                case S_IFIFO:   nfifo++;    break;
                case S_IFLNK:   nslink++;   break;
                case S_IFSOCK:  nsock++;    break;
                case S_IFDIR:
                    err_dump("for S_IFDIR for %s", path);
                break;
            }
            break;

        case FTW_D:
            ndir++;
            break;

        case FTW_NS:
            err_ret("stat error for %s", path);
            break;

        case FTW_DNR:
            err_ret("can't read dir %s", path);
            break;
    }

    return 0;
}
